/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { RuleListUtil } from "../../utils/common/DefectsList";
import { ArkFile, ts } from "arkanalyzer/lib";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { AstTreeUtils } from "arkanalyzer";
import { Rule } from "../../model/Rule";
import { Defects, IssueReport } from "../../model/Defects";
import { RuleFix } from '../../model/Fix';
const gMetaData: BaseMetaData = {
  severity: 3,
  ruleDocPath: "docs/prefer-regexp-exec.md",
  description: "Use the `RegExp#exec()`method instead.",
};

interface LocationInfo {
  fileName: string;
  line: number;
  startCol: number; // 整个表达式的开始列号
  endCol: number;   // 整个表达式的结束列号
  Qualifier: string;
  object: string;   // 调用方，例如 'something'
  argument: string; // 参数，例如 '/thing/'
  matchStartCol: number; // 新增：match 方法的开始列号
}

export class PreferRegexpExecCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  public registerMatchers(): MatcherCallback[] {
    return [{
      matcher: { matcherType: MatcherTypes.FILE },
      callback: this.check,
    }];
  }

  public check = (target: ArkFile) => {
    const code = target.getCode()?.toString() ?? '';
    const sourceFile = AstTreeUtils.getASTNode(target.getName(), code);
    const program = ts.createProgram([target.getFilePath()], {});
    const checker = program.getTypeChecker();
    const filePath = target.getFilePath();
    this.checkForRegExpExec(sourceFile, checker).forEach((loc) => {
      this.addIssueReportNodeFix(sourceFile, loc, filePath);
    });
  }

  private checkForRegExpExec(sourceFile: ts.SourceFile, checker: ts.TypeChecker): LocationInfo[] {
    const issues: LocationInfo[] = [];
    const visitNode = (node: ts.Node) => {
      if (ts.isCallExpression(node) &&
        node.arguments.length === 1 &&
        ts.isPropertyAccessExpression(node.expression) &&
        node.expression.name.text === 'match') {
        const regexArg = node.arguments[0];
        if (!this.isRegExpType(regexArg, checker)) return;
        const hasGlobalFlag = this.checkGlobalFlag(regexArg, checker);
        if (!hasGlobalFlag) {
          const pos = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart());
          const endPos = ts.getLineAndCharacterOfPosition(sourceFile, node.getEnd());
          const matchPos = ts.getLineAndCharacterOfPosition(sourceFile, node.expression.name.getStart()); // match 方法的起始位置 
          const object = node.expression.expression.getText(sourceFile);
          const argument = node.arguments[0].getText(sourceFile);
          const locationInfo: LocationInfo = {
            fileName: sourceFile.fileName,
            line: pos.line + 1,
            startCol: pos.character + 1,
            endCol: endPos.character + 1,
            Qualifier: node.expression.name.text,
            object,
            argument,
            matchStartCol: matchPos.character + 1
          };
          issues.push(locationInfo);
        }
      }
      ts.forEachChild(node, visitNode);
    };
    visitNode(sourceFile);
    return issues;
  }
  private isRegExpType(argNode: ts.Node, checker: ts.TypeChecker): boolean {
    // 处理字面量正则
    if (ts.isRegularExpressionLiteral(argNode)) return true;

    // 新增类型名称判断
    const type = checker.getTypeAtLocation(argNode);
    if (checker.typeToString(type) === 'RegExp') return true;

    // 处理通过new RegExp创建的情况
    const regExpSymbol = checker.getSymbolAtLocation(
      ts.factory.createIdentifier("RegExp")
    );
    return !!type.getSymbol()?.declarations?.some(declaration => {
      const declarationType = checker.getTypeAtLocation(declaration);
      return declarationType.getSymbol() === regExpSymbol;
    });
  }
  private checkGlobalFlag(regexArg: ts.Node, checker: ts.TypeChecker): boolean {
    // 新增：检查是否为类型声明而没有初始化的具体值
    if (ts.isIdentifier(regexArg)) {
      const symbol = checker.getSymbolAtLocation(regexArg);
      if (symbol && symbol.declarations) {
        for (const declaration of symbol.declarations) {
          if (ts.isVariableDeclaration(declaration) && !declaration.initializer) {
            // 如果是变量声明但没有初始化表达式，则假设有全局标志以避免误报
            return true;
          }
        }
      }
    }

    // 现有的逻辑保持不变
    if (ts.isCallExpression(regexArg) &&
      ts.isIdentifier(regexArg.expression) &&
      regexArg.expression.text === 'RegExp') {
      const flagsNode = regexArg.arguments?.[1];
      return !!(
        flagsNode &&
        ts.isStringLiteral(flagsNode) &&
        flagsNode.text.includes('g')
      );
    }

    // 场景1：正则字面量 /pattern/g
    if (ts.isRegularExpressionLiteral(regexArg)) {
      const parts = regexArg.getText().split('/');
      return parts.length >= 3 ? parts[2].includes('g') : false;
    }

    // 场景2：new RegExp("pattern", "g")
    if (ts.isNewExpression(regexArg)) {
      const flagsNode = regexArg.arguments?.[1];
      return !!(
        flagsNode &&
        ts.isStringLiteral(flagsNode) &&
        flagsNode.text.includes('g')
      );
    }

    // 场景3：变量引用
    if (ts.isIdentifier(regexArg)) {
      const symbol = checker.getSymbolAtLocation(regexArg);
      if (symbol?.declarations) {
        return symbol.declarations.some(declaration =>
          this.traceRegExpFlags(declaration, checker)
        );
      }
    }
    return false;
  }
  private traceRegExpFlags(declaration: ts.Node, checker: ts.TypeChecker): boolean {
    if (ts.isVariableDeclaration(declaration)) {
      const symbol = checker.getSymbolAtLocation(declaration.name);
      if (!symbol?.declarations) return false;

      return symbol.declarations.some(decl =>
        ts.isVariableDeclaration(decl) &&
        decl.initializer &&
        this.checkGlobalFlag(decl.initializer, checker)
      );
    }
    return false;
  }

  private ruleFix(sourceFile: ts.SourceFile, loc: LocationInfo): RuleFix {
    const [start, end] = this.getFixRange(sourceFile, loc);
    // 直接使用 LocationInfo 中的 object 和 argument
    const fixText = `${loc.argument}.exec(${loc.object})`;
    return { range: [start + 1, end], text: fixText };
  }

  // 获取起始位置和结束位置
  private getFixRange(sourceFile: ts.SourceFile, loc: LocationInfo): [number, number] {
    // 获取 match 方法的开始位置
    const startPosition = sourceFile.getPositionOfLineAndCharacter(loc.line - 1, loc.startCol - 1);
    // 获取 match 方法的结束位置
    const endPosition = sourceFile.getPositionOfLineAndCharacter(loc.line - 1, loc.endCol - 1);
    return [startPosition, endPosition];
  }
  private addIssueReportNodeFix(sourceFile: ts.SourceFile, loc: LocationInfo, filePath: string) {
    const severity = this.rule.alert ?? this.metaData.severity;
    const fixKey = loc.line + '%' + loc.startCol + '%' + loc.endCol + '%' + this.rule.ruleId;
    const mergeKey = filePath + '%' + fixKey + '%' + this.metaData.description;
    const defect = new Defects(loc.line, loc.matchStartCol, this.metaData.description, severity, this.rule.ruleId,
      mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
    let fix: RuleFix = this.ruleFix(sourceFile, loc);
    this.issues.push(new IssueReport(defect, fix));
    RuleListUtil.push(defect);
  }
} 